﻿/*
 * Author: Roy Scheefhals
 * Modified by Martijn Koopman
 * 
 * Growing Selection Blob Detection ( this version does not have recursion )
 * This class also fiters out wrong blobs ( has to be put into new class though )
 * */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

namespace LibraryCS {
    public class ColorBasedRegionGrowingSegmentation : VisionAlgorithm {
        public Bitmap boundedBlobs;

        private VImage image;
        private bool Verbose = false;

        private double[][] inputpixels;
        private int[] pixelslabeled; // pixels 
        private double[] range;

        private int givenlabel;

        //arrays used for non recursive checking of all pixels
        private int[] ToCheck1x;
        private int[] ToCheck1y;

        private int[] ToCheck2x;
        private int[] ToCheck2y;

        private int amountToCheckList1;
        private int amountToCheckList2;

        private Blob curBlob;

        public ColorBasedRegionGrowingSegmentation(String name) : base(name) { }

        public override System.Drawing.Bitmap DoAlgorithm(System.Drawing.Bitmap sourceImage)
        {
            throw new NotImplementedException();
        }

        public VImage[] DetectBlobs(VImage sourceImage) {
            // Initialize Variables
            image = sourceImage;
            givenlabel = 0;
            curBlob = new Blob();
            amountToCheckList1 = 0;
            amountToCheckList2 = 0;
            image.calculateHSV();
            inputpixels = image.HSV;
            pixelslabeled = new int[image.Height * image.Width];

            ToCheck1x = new int[100000];
            ToCheck1y = new int[100000];

            ToCheck2x = new int[100000];
            ToCheck2y = new int[100000];

            // Start the growing selection
            List<Blob> blobList = startGrowingSelection();

            // Filter blobs
            blobList = filterBlobs(blobList);

            if (Verbose) {
                // Initialize input/output image
                image = sourceImage;

                boundedBlobs = drawBoundingBox(blobList);
            }

            VImage[] candidates = new VImage[blobList.Count];

            for (int i = 0; i < blobList.Count; i++) {
                candidates[i] = new VImage(blobList[i].maxx, blobList[i].maxy);

                int ammountOfPixels = blobList[i].maxx * blobList[i].maxy;
                int pIndex = blobList[i].miny * image.Width + blobList[i].minx;
                for (int p = pIndex; p < ammountOfPixels; p++) {
                    if (p < blobList[i].minx || p > blobList[i].maxx) {
                        continue;
                    }
                    if (p < blobList[i].miny || p > blobList[i].maxy) {
                        continue;
                    }
                    candidates[i].RedPixels[p] = image.RedPixels[p];
                    candidates[i].GreenPixels[p] = image.GreenPixels[p];
                    candidates[i].BluePixels[p] = image.BluePixels[p];
                }
                candidates[i].ApplyPixelChanges();
            }

            return candidates;
        }

        private List<Blob> filterBlobs(List<Blob> blobs) {
            // Filter small blobs
            List<Blob> smallBloblist = new List<Blob>();
            foreach (Blob blob in blobs) {
                int blobWidth = blob.maxx - blob.minx;
                int blobHeight = blob.maxy - blob.miny;

                if (blobWidth > 130 && blobHeight > 30 && blobWidth > blobHeight) {
                    smallBloblist.Add(blob);
                }
            }

            // foreach blob
            foreach (Blob blob in smallBloblist) {
                int blobWidth = blob.maxx - blob.minx;
                int blobHeight = blob.maxy - blob.miny;

                int blobCenterX = blob.minx + (blobWidth / 2);
                int blobCenterY = blob.miny + (blobHeight / 2);

                // Set initial corners 
                // because it might be possible that one of the divided segments has no pixels
                blob.topLeft.X = blob.bottomLeft.X = blobCenterX;
                blob.topRight.X = blob.bottomRight.X = blobCenterX;
                blob.topLeft.Y = blob.topRight.Y = blobCenterY;
                blob.bottomLeft.Y = blob.bottomRight.Y = blobCenterY;

                // Calculate corners ( BY MARTIJN KOOPMAN )
                double maxDistance = 0;

                // Calculate top left corner
                // For each pixel P where Px < blobCenterX && Py < blobCenterY  
                for (int y = blob.miny; y < blobCenterY; y++) {
                    for (int x = blob.minx; x < blobCenterX; x++) {
                        int distanceX = blobCenterX - x;
                        int distanceY = blobCenterY - y;

                        if (pixelslabeled[y * image.Width + x] == blob.label) {
                            double distance = distanceX + distanceY;// Math.Sqrt((Math.Pow(distanceX, 2) + Math.Pow(distanceY, 2)));
                            if (distance > maxDistance) {
                                maxDistance = distance;

                                // remember pixel
                                blob.topLeft.X = x;
                                blob.topLeft.Y = y;
                            }
                        }
                    }
                }

                // Calculate bottom left corner
                maxDistance = 0;
                for (int y = blobCenterY; y < blob.maxy; y++) {
                    for (int x = blob.minx; x < blobCenterX; x++) {
                        int distanceX = blobCenterX - x;
                        int distanceY = y - blobCenterY;

                        if (pixelslabeled[y * image.Width + x] == blob.label) {
                            double distance = distanceX + distanceY;// Math.Sqrt((Math.Pow(distanceX, 2) + Math.Pow(distanceY, 2)));
                            if (distance >= maxDistance) {
                                maxDistance = distance;

                                // remember pixel
                                blob.bottomLeft.X = x;
                                blob.bottomLeft.Y = y;
                            }
                        }
                    }
                }

                // Calculate top right corner
                maxDistance = 0;
                // for each pixel P where Px > blobCenterX && Py < blobCenterY
                for (int y = blob.miny; y < blobCenterY; y++) {
                    for (int x = blobCenterX; x < blob.maxx; x++) {
                        int distanceX = x - blobCenterX;
                        int distanceY = blobCenterY - y;

                        if (pixelslabeled[y * image.Width + x] == blob.label) {
                            double distance = distanceX + distanceY;// Math.Sqrt((Math.Pow(distanceX, 2) + Math.Pow(distanceY, 2)));
                            if (distance > maxDistance) {
                                maxDistance = distance;

                                // remember pixel
                                blob.topRight.X = x;
                                blob.topRight.Y = y;
                            }
                        }
                    }
                }

                // Calculate bottom right corner
                maxDistance = 0;
                // for each pixel P where Px > blobCenterX && Py > blobCenterY
                for (int y = blobCenterY; y < blob.maxy; y++) {
                    for (int x = blobCenterX; x < blob.maxx; x++) {
                        int distanceX = x - blobCenterX;
                        int distanceY = y - blobCenterY;

                        if (pixelslabeled[y * image.Width + x] == blob.label) {
                            double distance = distanceX + distanceY;// Math.Sqrt((Math.Pow(distanceX, 2) + Math.Pow(distanceY, 2)));
                            if (distance >= maxDistance) {
                                maxDistance = distance;

                                // remember pixel
                                blob.bottomRight.X = x;
                                blob.bottomRight.Y = y;
                            }
                        }
                    }
                }

                Debug.WriteLine("Blob: " + blob.ToString());
            }

            List<Blob> FilteredBlobs = new List<Blob>();

            //Filter all the blobs to try to get one blob ( BY ROY SCHEEFHALS )
            foreach (Blob b in smallBloblist) {
                //Do not include blobs:
                //with Left side and right side corners too close together
                if (b.bottomLeft.Y - b.topLeft.Y < 15 || b.bottomRight.Y - b.topRight.Y < 15) {
                    continue;
                }

                //with the diagonal corners too close together
                if (b.bottomRight.X - b.topLeft.X < 100 || b.topRight.X - b.bottomLeft.X < 100) {
                    continue;
                }

                //the diagonal points too close together on the Y axel
                if (b.bottomRight.Y - b.topLeft.Y < 15 || b.bottomLeft.Y - b.topRight.Y < 15) {
                    continue;
                }

                //Filter wrong width height ratios
                //rechst en onder ... links en boven vergelijken
                double ratioRightBottom = (b.topRight.X - b.topLeft.X) / (b.bottomRight.Y - b.topRight.Y);
                if (ratioRightBottom > 8)// drop the blob with a too high ratio
                {
                    continue;
                }

                double ratioLeftTop = (b.bottomRight.X - b.bottomLeft.X) / (b.bottomLeft.Y - b.topLeft.Y);
                if (ratioLeftTop > 8)// drop the blob with a too high ratio
                {
                    continue;
                }

                //are the vertical/horizontal edges not parallel ?
                int leftEdgeDif = b.topLeft.X - b.bottomLeft.X;
                int rightEdgeDif = b.topRight.X - b.bottomRight.X;
                int difference = Math.Abs(leftEdgeDif - rightEdgeDif);
                Debug.WriteLine("blob: " + b.topLeft.X + ", " + b.topLeft.Y + "\ndifference: " + difference + "\nwidth" + (b.maxx - b.minx));
                if (difference > (0.3 * (b.maxx - b.minx))) {
                    continue;
                }

                FilteredBlobs.Add(b);
            }

            return FilteredBlobs;
        }

        public Bitmap drawBoundingBox(List<Blob> blobList) {
            VImage output = new VImage(image.Width, image.Height);

            // Draw blobs on image
            int colors = 256 * 256 * 256;
            int increment = 15;
            if (blobList.Count != 0) {
                increment = colors / blobList.Count;
            }

            for (int i = 0; i < pixelslabeled.Length; i++) {
                if (pixelslabeled[i] == 0)
                    continue;

                int x = i % output.Width;
                int y = i / output.Width;

                output.SetSinglePixel(x, y, 0x0000FF);
            }

            // Draw bounding box around the max and min x and y
            // Draw polygon of the blob corners
            Graphics g = Graphics.FromImage(output.localBitmap);
            foreach (Blob b in blobList) {
                g.DrawRectangle(Pens.Red, new Rectangle(b.minx, b.miny, b.maxx - b.minx, b.maxy - b.miny));

                Point topLeftCorner = new Point(b.topLeft.X, b.topLeft.Y);
                Point bottomLeftCorner = new Point(b.bottomLeft.X, b.bottomLeft.Y);
                Point topRightCorner = new Point(b.topRight.X, b.topRight.Y);
                Point bottomRightCorner = new Point(b.bottomRight.X, b.bottomRight.Y);

                //Draw the lines between the corners of the blob
                g.DrawLine(Pens.White, topLeftCorner, topRightCorner);
                g.DrawLine(Pens.White, topLeftCorner, bottomLeftCorner);
                g.DrawLine(Pens.White, topRightCorner, bottomRightCorner);
                g.DrawLine(Pens.White, bottomLeftCorner, bottomRightCorner);
            }

            output.ApplyPixelChanges();

            return output.localBitmap;
        }

        private List<Blob> startGrowingSelection() {
            List<Blob> blobList = new List<Blob>();
            givenlabel = 1;
            int curpixel;

            // Process whole image
            for (int y = 1; y < image.Height - 1; y++) {
                for (int x = 1; x < image.Width - 1; x++) {
                    curpixel = (y * image.Width) + x;

                    range = new double[] { image.HSV[curpixel][0] - 10, image.HSV[curpixel][0] + 10, image.HSV[curpixel][1] - 0.1, image.HSV[curpixel][1] + 0.1, image.HSV[curpixel][2] - 0.1, image.HSV[curpixel][2] + 0.1 };

                    // Is there a pixel that hasnt been processed yet ?
                    if (pixelslabeled[curpixel] == 0 && isInRange(curpixel, range)) {
                        // Initialize the blob and give first pixel his label
                        pixelslabeled[curpixel] = givenlabel;
                        curBlob.amountOfPixels++;
                        curBlob.label = givenlabel;
                        curBlob.maxx = curBlob.minx = x;
                        curBlob.maxy = curBlob.miny = y;

                        // Start growing blob detection // Returns true if more pixels are left in the image
                        if (checkarea(givenlabel, x, y, 1) == true) {
                            // Start processing the list
                            processList(givenlabel);

                            // After processlist, the blob has been found
                        } else {
                            // Blob was only 1 pixel large

                            curBlob = new Blob();
                            continue;
                        }

                        if (curBlob.amountOfPixels < 1000) {
                            // 1000 pixels are too few for a whole License plate. 
                            // This amount needs testing though.
                            curBlob = new Blob();
                            continue;
                        }

                        // Initialize for new blob
                        blobList.Add(curBlob);
                        curBlob = new Blob();
                        givenlabel++;
                    }
                }
            }

            return blobList;
        }

        private void processList(int label) {
            Boolean ProcessablePixels;

            //main loop
            for (; ; ) {
                ProcessablePixels = false;

                //process list 1
                for (int i = 0; i < amountToCheckList1; i++) {
                    ProcessablePixels = true;
                    checkarea(label, ToCheck1x[i], ToCheck1y[i], 2);
                }
                amountToCheckList1 = 0;

                // process list 2
                for (int i = 0; i < amountToCheckList2; i++) {
                    ProcessablePixels = true;
                    checkarea(label, ToCheck2x[i], ToCheck2y[i], 1);
                }
                amountToCheckList2 = 0;

                // both list were empty so there are no connecting pixels anymore ( get out of the main loop )
                if (ProcessablePixels == false) {
                    break;
                }
            }
        }

        private Boolean checkarea(int label, int startx, int starty, int insertToList) {
            // boolean to check if there are ANY new items to process
            Boolean changed = false;

            // check the 8 connected pixels around the startpixel
            for (int patchy = -1; patchy < 2; patchy++) {
                for (int patchx = -1; patchx < 2; patchx++) {
                    int curpixel = ((starty + patchy) * image.Width) + (startx + patchx);

                    // stay inside the picture
                    if ((startx + patchx) >= 0 &&
                        (startx + patchx) < image.Width &&
                        (starty + patchy) >= 0 &&
                        (starty + patchy) < image.Height) {
                        //there is a foreground pixel without a label
                        if (pixelslabeled[curpixel] == 0 && isInRange(curpixel, range)) {
                            pixelslabeled[curpixel] = label;
                            curBlob.amountOfPixels++;

                            // keep track of the bounding box for the blob
                            if ((startx + patchx) > curBlob.maxx)
                                curBlob.maxx = startx + patchx;
                            if ((startx + patchx) < curBlob.minx)
                                curBlob.minx = startx + patchx;

                            if ((starty + patchy) > curBlob.maxy)
                                curBlob.maxy = starty + patchy;
                            if ((starty + patchy) < curBlob.miny)
                                curBlob.miny = starty + patchy;

                            //insert to list that is NOT currently being processed
                            if (insertToList == 1) {
                                ToCheck1x[amountToCheckList1] = startx + patchx;
                                ToCheck1y[amountToCheckList1] = starty + patchy;
                                amountToCheckList1++;
                            } else {
                                ToCheck2x[amountToCheckList2] = startx + patchx;
                                ToCheck2y[amountToCheckList2] = starty + patchy;
                                amountToCheckList2++;
                            }

                            changed = true;
                        }
                    }
                }
            }

            return changed;
        }

        private bool isInRange(int pixel, double[] range) {
            if (inputpixels[pixel][0] > range[0] && inputpixels[pixel][0] < range[1]) {
                if (inputpixels[pixel][1] > range[2] && inputpixels[pixel][0] < range[3]) {
                    if (inputpixels[pixel][2] > range[4] && inputpixels[pixel][2] < range[5]) {
                        return true;
                    }
                }
            }

            return false;
        }

        #region GUI Functions
        /* 
         * Toegevoegd door: Duncan Jenkins
        */
        public override SettingsPropertyBox createControls() {
            SettingsPropertyBox propertyControls = new SettingsPropertyBox();

            //textbox for Colorchannels
            Label Vlb = new Label();
            Vlb.Text = "Verbose Mode:";
            Vlb.Dock = DockStyle.Right;

            CheckBox Vcb = new CheckBox();
            Vcb.Dock = DockStyle.Left;

            // Add the controls to the list
            propertyControls.AddLine(Vlb, Vcb, null, null);

            // Return the list
            return propertyControls;
        }

        /* 
         * Toegevoegd door: Duncan Jenkins
        */
        public override void setSettings(List<object> settings) {
            try {
                Verbose = (bool)settings[0];

                Debug.WriteLine("Settings entered: " + Verbose);
            } catch (FormatException) {
                MessageBox.Show("Wrong Format has been entered.\nPlease enter integers\nNew Settings have been discarded for " + Name, "Format Error", MessageBoxButtons.OK, MessageBoxIcon.Hand);
            }
        }
        #endregion
    }
}
